#ifndef UTILS_H
#define UTILS_H

#include "GlobalConfig.h"
#include <vector>
#include <wx/regex.h>
#include <fstream>
#include <wx/log.h>
#include <wx/thread.h>
#include <map>
#include <vector>
#include <set>

#define UT_SPECIAL_CHARS(strInput) \
    SQLInjection::GetInstance().specialCharCheck(strInput);

#define UT_URLENCODE_ASCII(strInput) \
    URLEncoder::GetInstance().AsciiParse(strInput)

#define UT_URLENCODE_UTF8(strInput) \
    URLEncoder::GetInstance().UTF8Parse(strInput)

#ifdef _LOG
    #define LOGMAN  CLog::GetInstance()
#endif

#ifndef LOG_USE_CPP_STREAM
    #define LOG_USE_CPP_STREAM
#endif


#define XML_SANITY_CHECK(table) \
    XMLIntegrityValidator::GetInstance().sanityCheck(table);


#define URLENCODE_BUFFER_SIZE 64


typedef std::vector< std::map<wxString , wxString> >   tableType;

class IPlainTextFilter
{
    protected:
        std::vector<wxString> charBlacklist;  //Should be all in unicode, just single char per element
        wxRegEx re;
        wxCriticalSection filter_CS;

        virtual void setBlacklist()=0;
        virtual void Init()=0;
    public:
        IPlainTextFilter(){};
        virtual ~IPlainTextFilter(){};

        virtual void specialCharCheck(wxString& inStr)=0;
};

class SQLInjection: public IPlainTextFilter
{
    private:
        SQLInjection(const SQLInjection&);
        SQLInjection& operator=(const SQLInjection&);
        SQLInjection();
        virtual ~SQLInjection();

        void setBlacklist();
        void Init();
    public:
        static SQLInjection& GetInstance();

        inline void specialCharCheck(wxString& inStr);
};

void SQLInjection::specialCharCheck(wxString& inStr)
{
    wxCriticalSectionLocker lockSQLInjection(filter_CS);
    if(re.Matches(inStr))
        re.Replace(&inStr, wxString(_T("")) );
}


/**************************************************************
This XML validator class working this way
(only modifys column name, fields values are ignroed):
1. count every column name
2. find column(s) which count==1 or N-1
3. if count==1 added to errorList , that's a corrupted column
    3.1 catch count==1
    3.2 try to catch a count==N-1
    3.3 if 3.2 find a N-1, then compare similarity by RegEx matching
    3.4 if 3.3 matched a corrupted column , add that N-1 to ref field in erroList
4. correct errors using reference
**************************************************************/
class XMLIntegrityValidator
{
    #ifdef UNIT_TEST
    friend class UnitTest_XMLValidator;
    #endif

    class RefFormat
    {
        public :
        RefFormat(const wxString& newRef, const int8_t newWeight, const size_t _hitrowsize)
            :refName(newRef)
            ,weight(newWeight)
            ,refHitRowSize(_hitrowsize)
        {

        }

        wxString refName;
        int8_t weight;
        size_t refHitRowSize;  //do not destroy m_statsData before this

        bool operator<(const RefFormat& _other) const {return weight<_other.weight;}    //const function is required
        bool operator==(const RefFormat& _other) const {return refName==_other.refName;}
    };

    class XMLErrorData
    {
        public:

        XMLErrorData(const wxString& _error, const std::vector<size_t>& _indices)
            :wrongTagName(_error)
            ,nextTagName("")
            ,hitRows(_indices)
            ,refMatches()   //required , otherwise refMatches is reported as undefined
        {
            assert( ! _indices.empty());
        }

        wxString wrongTagName;
        wxString nextTagName;
        std::vector<size_t> hitRows;
        std::vector<RefFormat> refMatches;  //vector<refStr, weight>

        bool operator==(const XMLErrorData& _other) const {return wrongTagName==_other.wrongTagName;}
        //bool operator<(const XMLErrorData& _other) const {return wrongTagName<_other.wrongTagName;}
    };

    private :
        XMLIntegrityValidator(const XMLIntegrityValidator&);
        XMLIntegrityValidator& operator=(const XMLIntegrityValidator&);
        XMLIntegrityValidator();
        virtual ~XMLIntegrityValidator();

        wxCriticalSection XMLvalidator_CS;
        std::map<wxString, std::vector<size_t> > m_statsData;     //map<XMLtagName , sameNameCount>
        std::vector<XMLErrorData> m_errors;  //used for identify multiple RE matches

        //acutally there's a threshold , not 1 and N-1 , but N*threshold and 1-N*threshold
        const float ERROR_IDENTIFY_THRESHOLD;

        void sampleXML(const tableType& table);
        bool isValid(const tableType& table);
        void correctErrors(tableType& table);   //const feature is violated by operator[] of table
        void findRef(tableType& table);

        inline void clearData();    //to keep operation atomic
    public:
        static XMLIntegrityValidator& GetInstance();
        void sanityCheck(tableType& table);   //for the sina specific errors
};

/********************************************************
*Important !!!
*
*When you change the active target of wxLog,
*you won't be able to use wxMessage , or app crashes
*
*'Cause the wxMessageBox depends on wxLogGui as active wxLog target
*********************************************************/

class CLog
{
    private :

    #ifdef LOG_USE_CPP_STREAM
    std::ofstream outfile;
    #endif

    #ifdef LOG_USE_C_FILE
    FILE* pLogFile;
    #endif

    size_t errorMsgCount;
    wxLog* threadLogTarget;
    wxLog* threadGuiTarget;
    wxCriticalSection log_CS;

    CLog(const wxString& fileName = "log.txt");
    CLog(const CLog&);
    CLog& operator=(const CLog&);
    virtual ~CLog();

    public :
    static CLog& GetInstance();
    void logError(const wxString& logMsg);
    void logInfo(const wxString& logMsg);

    bool hasAnyError()
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        return errorMsgCount>0;
    }

    void resetErrorCount()
    {
        wxCriticalSectionLocker lockLOGMAN(log_CS);
        errorMsgCount = 0;
    }
};

class URLEncoder
{
    typedef unsigned char BYTE;

    private:
        URLEncoder(const URLEncoder&);
        URLEncoder& operator=(const URLEncoder&);
        URLEncoder();
        virtual ~URLEncoder(){};

        inline BYTE toHex(const BYTE &x);
        inline bool isUnreservedSpecialChar(const BYTE &c); // http://en.wikipedia.org/wiki/Percent-encoding

        std::vector<wxString> outputBufferQueue;
        size_t  bufferIndex;
        wxCriticalSection URLencoder_CS;
    public:
        static URLEncoder& GetInstance();
        wxString& AsciiParse(const wxString& rawURL);
        wxString& UTF8Parse(const wxString& rawURL);
};


#endif //UTILS_H
